﻿
namespace Patterns4Net.ArchitectureExplorer.Diagram.Services
{
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Core;

    /// <summary>
    /// Merges relations when possible. 
    /// This update of relations should happen as the last relations update.
    /// </summary>
    [DiagramUpdaterExport(Order = 1000)]
    public class MergeRelations : IDiagramUpdater
    {
        public void UpdateDiagram(Diagram diagram, TextWriter writer)
        {
            var merged = GetMerged(diagram.Relations).ToArray();
            // we remove all the relations that can be merged
            foreach (var r in merged.SelectMany(x => new[] { x.OriginalRelations.Relation1, x.OriginalRelations.Relation2 }))
            {
                diagram.RemoveRelation(r);
            }

            // and add merged ones
            merged.Select(x => x.MergedRelation).ForEach(diagram.AddRelation);
        }

        public static IEnumerable<Relation> Merge(IEnumerable<Relation> relations)
        {
            var merged = GetMerged(relations);
            var remove = merged.SelectMany(x => new[] { x.OriginalRelations.Relation1, x.OriginalRelations.Relation2 });
            return merged.Select(x => x.MergedRelation).Union(relations.Except(remove));                
        }

        /// <summary>
        /// Helper method that returns a list of merged relations and for each a pair the 
        /// two original relations that has been merged.
        /// </summary>
        private static IEnumerable<MergeResult> GetMerged(IEnumerable<Relation> relations)
        {
            var result = 
                   from r1 in relations
                   from r2 in relations
                   where r1 != r2
                   where r1.CanBeMerged
                   where r1.Source == r2.Target && r2.Source == r1.Target && r1.GetType() == r2.GetType()
                   let pair = new RelationPair { Relation1 = r1, Relation2 = r2 }
                   let merged =
                       pair.Relation1.CreateBidirectional(
                           pair.Relation1.Source,
                           pair.Relation1.Target,
                           pair.Relation1.TargetCardinality,
                           pair.Relation2.TargetCardinality)
                   // TargetCardinality is correct here ^, we are using the second relation, which goes backwards
                   select new MergeResult { MergedRelation = merged, OriginalRelations = pair };
            return result.Distinct((r1, r2) =>
                                   r1.OriginalRelations.Relation1 == r2.OriginalRelations.Relation2 &&
                                   r1.OriginalRelations.Relation2 == r2.OriginalRelations.Relation1);
        }

        private class MergeResult
        {
            public RelationPair OriginalRelations { get; set; }

            public Relation MergedRelation { get; set; }
        }

        private class RelationPair
        {
            public Relation Relation1 { get; set; }

            public Relation Relation2 { get; set; }
        }
    }
}
